package cn.okcode.slowjson.parser;

import cn.okcode.slowjson.Config;
import cn.okcode.slowjson.JsonException;
import cn.okcode.slowjson.internal.CharUtils;

import java.util.Stack;


/**
 * json词法分析器实现
 *
 * @author pyk13
 */
public class JsonLexer {
    /**
     * 配置
     */
    private final Config config;

    /**
     * 结束符
     */
    protected static final char EOF = '\0';

    /**
     * json源文件
     */
    protected String source;

    /**
     * 最新字符索引
     */
    protected int pos = -1;

    /**
     * 最新读取的字符
     */
    private char current;

    /**
     * 最新得到的Token
     */
    private Token token;

    /**
     * 行号
     */
    public int rowIdx;

    /**
     * 从行首开始的位置
     */
    public int colIdx;

    /**
     * 当前token的长度
     */
    private int tokenLength;

    public JsonLexer(String source, Config config) {
        rowIdx = 1;
        colIdx = 0;
        //第一个字符默认为空
        current = ' ';
        this.source = source;
        this.contextStack = new Stack<>();
        this.config = config;
    }

    private final Stack<Object[]> contextStack;

    public Token getToken() {
        return token;
    }

    protected char next() {
        if (current == '\n') {
            colIdx = 0;
            rowIdx++;
        }
        colIdx++;
        tokenLength++;
        pos++;
        if (pos < source.length()) {
            current = source.charAt(pos);
        } else {
            current = EOF;
        }
        return current;
    }

    protected char peek() {
        return pos + 1 < source.length() ? source.charAt(pos + 1) : EOF;
    }

    public void nextToken() {
        this.token = doNextToken();
        this.tokenLength = 0;
    }

    public Token peekToken() {
        //保存状态
        pushContext();

        nextToken();
        Token peekToken = this.token;

        //恢复状态
        popContext();
        return peekToken;
    }

    private void pushContext() {
        Object[] backups = new Object[6];
        backups[0] = this.pos;
        backups[1] = this.rowIdx;
        backups[2] = this.colIdx;
        backups[3] = this.current;
        backups[4] = this.token;
        backups[5] = this.tokenLength;
        contextStack.push(backups);
    }

    private void popContext() {
        Object[] backups = contextStack.pop();
        this.pos = (int) backups[0];
        this.rowIdx = (int) backups[1];
        this.colIdx = (int) backups[2];
        this.current = (char) backups[3];
        this.token = (Token) backups[4];
        this.tokenLength = (int) backups[5];
    }

    /**
     * 取词
     *
     * @return token
     */
    protected Token doNextToken() {
        //skip space tab \r \n \t
        skipIgnore();

        //skip comment
        skipComment();
        Token token;
        switch (current) {
            case '"':
                token = matchString();
                break;
            case EOF:
                token = new Token(TokenKind.EOF);
                System.out.println("解析结束");
                break;
            case '{':
                token = new Token(TokenKind.OBJ_LEFT);
                next();
                break;
            case '}':
                token = new Token(TokenKind.OBJ_RIGHT);
                next();
                break;
            case '[':
                token = new Token(TokenKind.ARR_LEFT);
                next();
                break;
            case ']':
                token = new Token(TokenKind.ARR_RIGHT);
                next();
                break;
            case ',':
                token = new Token(TokenKind.COMMA);
                next();
                break;
            case ':':
                token = new Token(TokenKind.COLON);
                next();
                break;
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
            case '+':
            case '-':
            case '.':
                token = matchNumber();
                break;
            default:
                if (CharUtils.isAlpha(current) || current == '_') {
                    token = matchIdentifier();
                } else {
                    throw new RuntimeException("不能识别的字符'" + current + "'");
                }
        }
        return token;
    }

    private Token matchNumber() {
        boolean isFloat = false;
        int start = pos;

        //尝试读取符号
        char ch = current;
        if (ch == '-' || ch == '+') {
            ch = next();
        }

        //尝试读取纯数字
        int numCount = 0;
        while (ch >= '0' && ch <= '9') {
            ch = next();
            numCount++;
        }

        //尝试读取小数
        if (ch == '.') {
            isFloat = true;
            while (true) {
                ch = next();
                if (ch >= '0' && ch <= '9') {
                    numCount++;
                } else {
                    break;
                }
            }
        }

        if (numCount == 0) {
            throw new JsonException("数字格式不对:" + source.substring(start, pos));
        }

        if (ch == 'L') {
            //长整形
            ch = next();
        } else if (ch == 'F') {
            //单精度浮点
            ch = next();
            isFloat = true;
        } else if (ch == 'D') {
            //双精度浮点
            ch = next();
            isFloat = true;
        } else if (ch == 'e' || ch == 'E') {
            //科学计数法
            ch = next();
            if (ch == '+' || ch == '-') {
                ch = next();
            }
            while (ch >= '0' && ch <= '9') {
                ch = next();
            }

            if (ch == 'D' || ch == 'F') {
                ch = next();
            }
            isFloat = true;
        }
        if (pos - start == 0) {
            throw new NumberFormatException("empty String");
        }
        String strValue = source.substring(start, pos);
        Double number = Double.parseDouble(strValue);
        return new Token(TokenKind.NUMBER, number);
    }

    public final static char[] ESCAPES = new char[128];

    static {
        ESCAPES['0'] = '\0';
        ESCAPES['1'] = '\1';
        ESCAPES['2'] = '\2';
        ESCAPES['3'] = '\3';
        ESCAPES['4'] = '\4';
        ESCAPES['5'] = '\5';
        ESCAPES['6'] = '\6';
        ESCAPES['7'] = '\7';
        ESCAPES['b'] = '\b';
        ESCAPES['t'] = '\t';
        ESCAPES['n'] = '\n';
        ESCAPES['f'] = '\f';
        ESCAPES['r'] = '\r';
        ESCAPES['"'] = '\"';
        ESCAPES['\\'] = '\\';
        ESCAPES['v'] = '\u000B';
        ESCAPES['/'] = '/';
    }

    /**
     * 以下代码从fastjson弄过来~
     * <p>
     * digits数组说明：
     * 数组下标是字符的ASCII值，数组值是字符对应的16进制值
     * 即：
     * 0-9:
     * digits['0'] = 0;
     * ...
     * digits['9'] = 9;
     * <p>
     * a,b,c,d,e,f
     * digits['a'] = 10;
     * ...
     * digits['f'] = 15;
     * <p>
     * A,B,C,D,E,F:
     * digits['A'] = 10;
     * ...
     * digits['F'] = 15;
     * <p>
     * 只考虑0-9的时候 和下面方法等效：
     * Byte.parseByte(String.valueOf('7'))
     * Integer.parseInt(new String(new char[]{'4'}))
     */
    private final static int[] digits = new int[(int) 'f' + 1];

    static {
        for (int i = '0'; i <= '9'; ++i) {
            digits[i] = i - '0';
        }
        for (int i = 'a'; i <= 'f'; ++i) {
            digits[i] = (i - 'a') + 10;
        }
        for (int i = 'A'; i <= 'F'; ++i) {
            digits[i] = (i - 'A') + 10;
        }
    }

    private Token matchString() {
        next();
        StringBuilder builder = new StringBuilder();
        boolean foundEscape = false;
        int start = pos;
        int bugLength = source.length();
        while (pos < bugLength) {
            char c = current;
            if (c == '"') {
                break;
            } else if (c == '\\') {
                next();
                if (pos >= bugLength) {
                    throw new cn.okcode.slowjson.internal.JsonException("不能识别的字符：'\\'");
                }
                char ch = current;
                if (ESCAPES.length > ch && ESCAPES[ch] != 0) {
                    builder.append(ESCAPES[ch]);
                } else if (ch == 'x') {
                    next();
                    char x1 = next();
                    char x2 = next();

                    int x_val = digits[x1] * 16 + digits[x2];
                    char x_char = (char) x_val;
                    builder.append(x_char);
                    break;
                } else if (ch == 'u') {
                    char c1 = next();
                    char c2 = next();
                    char c3 = next();
                    char c4 = next();
                    int val = Integer.parseInt(new String(new char[]{c1, c2, c3, c4}), 16);
                    builder.append((char) val);
                    break;
                } else {
                    throw new cn.okcode.slowjson.internal.JsonException("unclosed string : " + ch);
                }
                next();
                foundEscape = true;
            } else {
                builder.append(current);
                next();
            }
        }
        next();
        return new Token(TokenKind.STRING, builder.toString());
    }

    /**
     * 匹配标识符
     * <p>
     * 字母或者下划线开头
     * <p>
     * 其它可以是字母数字
     * <p>
     * [a-zA-Z_]([a-zA-Z_0-9])+
     *
     * @return 是否匹配成功
     */
    private Token matchIdentifier() {
        StringBuilder builder = new StringBuilder();
        do {
            builder.append(current);
            next();
        } while (CharUtils.isDigitOrAlpha(current) || current == '_');
        String stringValue = builder.toString();
        switch (stringValue) {
            case "true":
            case "false":
                return new Token(TokenKind.TRUE, Boolean.parseBoolean(stringValue));
            case "null":
                return new Token(TokenKind.NULL);
            default:
                if (!config.isLoosePropertyMatching()) {
                    throw new JsonException(formatMessage("无法处理的字符"));
                }
                //标识符--用于扩展识别JavaScript风格的属性名--即没有引号
                return new Token(TokenKind.IDENTIFIER, stringValue);
        }
    }

    private void skipIgnore() {
        do {
            skipComment();
            while (CharUtils.isEmpty(current) || current == '\r' || current == '\n' || current == '\t' || current == ' ') {
                next();
            }
        } while (isComment());
    }

    private boolean isComment() {
        char nextChar = peek();
        return current == '/' && nextChar == '/';
    }

    private void skipComment() {
        if (!config.isSkipComment()) {
            return;
        }
        while (isComment()) {
            do {
                next();
            } while (current != '\n');
            next();
        }
    }

    /**
     * 处理控制字符，方便显示
     * 0～31及127(共33个)是控制字符或通信专用字符（其余为可显示字符）
     * 暂时只处理下面三个
     */
    private static final String[] SPECIALS = new String[128];

    static {
        SPECIALS['\n'] = "\\n";
        SPECIALS['\t'] = "\\n";
        SPECIALS['\r'] = "\\n";
        SPECIALS['\\'] = "\\";
    }

    public String formatMessage(String message) {
        String chr = String.valueOf(current);
        if (current == EOF) {
            chr = "[结束符]";
        } else {
            if (SPECIALS.length > current) {
                if (SPECIALS[current] != null) {
                    chr = SPECIALS[current];
                }
            }
        }
        return String.format("解析错误:%s,当前符号:%s,当前字符:'%s',%d行,%d列附近,\n相关字符串:'%s'",
                message, token, chr, rowIdx, colIdx - tokenLength, token.getStringValue());
    }
}
